﻿#include "Util.h"
#include "lang/Exception.h"
#include "io/ByteArrayOutputStream.h"
#include "lang/String.h"
#include "lang/System.h"
#include "lang/StringBuilder.h"
#include "lang/Math.h"
using namespace yzrilyzr_array;
using namespace yzrilyzr_io;
using namespace yzrilyzr_lang;

namespace yzrilyzr_util{
	void Util::readFully(InputStream & in, ByteArray & b){
		readFully(in, b, 0, b.length);
	}
	void Util::readFully(InputStream & in, ByteArray & b, u_index off, u_index len){
		u_index n=0;
		while(n < len){
			u_index count=in.read(b, off + n, len - n);
			if(count < 0)
				throw Exception("EOFException");
			n+=count;
		}
	}
	int32_t Util::readVarintFromStream(InputStream & in){
		int32_t value=0;
		int32_t length=0;
		int32_t n;
		do{
			n=in.read();
			if(n < 0) throw Exception("EOFException");
			value<<=7;
			value|=n & 0b1111111;
			length+=1;
			if(length > 5){
				throw Exception("VarInt is too big");
			}
		} while((n & 0b10000000) == 0b10000000);
		return value;
	}
	int32_t Util::readInt(InputStream & in){
		int32_t ch1=in.read();
		int32_t ch2=in.read();
		int32_t ch3=in.read();
		int32_t ch4=in.read();
		if((ch1 | ch2 | ch3 | ch4) < 0)
			throw Exception("EOFException");
		return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0));
	}
	ByteArray Util::read(InputStream & fi){
		try{
			ByteArrayOutputStream bao;
			ByteArray buf(8192);
			int ind;
			while((ind=fi.read(buf)) != -1){
				bao.write(buf, 0, ind);
			}
			bao.flush();
			bao.close();
			fi.close();
			return bao.toByteArray();
		} catch(IOException e){
			return ByteArray();
		}
	}
	ByteArray Util::readByteArray(InputStream & is, u_index lengthLimit, u_index * outLength){
		int32_t length=readInt(is);
		if(length > lengthLimit && lengthLimit > 0)throw Exception("length > limit");
		ByteArray arr(length);
		readFully(is, arr, 0, length);
		*outLength=length;
		return arr;
	}
	double Util::lerp(double startY, double endY, double x){
		double deltaY=endY - startY;
		return startY + deltaY * x;
	}
	double Util::clamp01(double x){
		return clamp(x, 0.0, 1.0);
	}
	float Util::clamp01(float x){
		return clamp(x, 0.0f, 1.0f);
	}
	int Util::NChoose(int n, int k){
		int divisor=1;
		int multiplier=n;
		int answer=1;
		k=Math::min(k, n - k);
		while(divisor <= k){
			answer=(answer * multiplier) / divisor;
			multiplier--;
			divisor++;
		}
		return answer;
	}
	double Util::sigmoid(double i, double minV, double maxV, double ampL, double ampR){
		double minV2=1 / (1 + exp(ampR));
		double maxV2=1 / (1 + exp(ampL));
		double escale=(maxV - minV) / (maxV2 - minV2);
		double offset=maxV - maxV2 * escale;
		return offset + escale / (1 + exp(i));
	}
	double Util::linearMap(double xStart, double xEnd, double yStart, double yEnd, double x){
		return ((yEnd - yStart) * (x - xStart) / (xEnd - xStart)) + yStart;
	}
	void Util::checkSize(u_index size){
		if(size < 0 || size>1024 * 1024 * 1024){
			throw IndexOutOfBoundsException("Check size failed");
		}
	}
	void Util::checkIndex(u_index i, u_index length){
		if(i < 0){
			throw IndexOutOfBoundsException("index < 0");
		}
		if(i >= length){
			throw IndexOutOfBoundsException("index (" + std::to_string(i) + ") >= length (" + std::to_string(length) + ")");
		}
	}
	void Util::checkRange(u_index arrayLength, u_index fromIndex, u_index toIndex){
		if(fromIndex > toIndex){
			throw yzrilyzr_lang::IllegalArgumentException(
				"fromIndex(" + std::to_string(fromIndex) + ") > toIndex(" + std::to_string(toIndex) + ")");
		}
		if(fromIndex < 0){
			throw yzrilyzr_lang::ArrayIndexOutOfBoundsException(fromIndex);
		}
		if(toIndex > arrayLength){
			throw yzrilyzr_lang::ArrayIndexOutOfBoundsException(toIndex);
		}
	}
	static constexpr const char* BASE64_ALPHABET="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
	ByteArray Util::b64(const yzrilyzr_lang::String & data){
		StringBuilder chars=data;
		ByteArrayOutputStream bao;
		for(int32_t i=0,j=chars.length();i < j;i++){
			uint32_t a=chars[i];
			if(a >= 48 && a <= 57) chars[i]+=-48 + 52;
			else if(a >= 65 && a <= 90) chars[i]-=65;
			else if(a >= 97 && a <= 122) chars[i]+=-97 + 26;
			else if(a == 43) chars[i]=62;
			else if(a == 47) chars[i]=63;
			else if(a == 61) chars[i]=64;
		}
		for(int32_t i=0, j=chars.length();i < j - 4;i+=4){
			uint32_t c=(chars[i] & 0xff) << 18 | (chars[i + 1] & 0xff) << 12 | (chars[i + 2] & 0xff) << 6 | (chars[i + 3] & 0xff);
			bao.write((c >> 16) & 0xff);
			bao.write((c >> 8) & 0xff);
			bao.write(c & 0xff);
		}
		int32_t i=chars.length() - 4;
		int32_t c=(chars[i] & 0xff) << 18 | (chars[i + 1] & 0xff) << 12 | (chars[i + 2] & 0xff) << 6 | (chars[i + 3] & 0xff);
		bao.write((c >> 16) & 0xff);
		if(chars[i + 2] != 64) bao.write((c >> 8) & 0xff);
		if(chars[i + 3] != 64) bao.write(c & 0xff);
		return bao.toByteArray();
	}
	 yzrilyzr_lang::String Util::b64(const yzrilyzr_array::ByteArray & data){
		 StringBuilder result;
		 int32_t ii=0;
		 for(;ii + 2 < data.length;ii+=3){
			 int32_t d1=((data[ii] & 0xff) << 16);
			 d1|=((data[ii + 1] & 0xff) << 8);
			 d1|=(data[ii + 2] & 0xff);
			 for(int32_t j=3;j >= 0;j--){
				 int32_t index=(d1 >>(j * 6)) & 0b111111;
				 result.append(BASE64_ALPHABET[index]);
			 }
		 }
		 if(ii < data.length){
			 int32_t c=data.length - ii;
			 if(c == 1){
				 int32_t d=data[data.length - 1] & 0xff;
				 result.append(BASE64_ALPHABET[d >> 2]);
				 result.append(BASE64_ALPHABET[(d << 4) & 0b111111]);
				 result.append("=");
				 result.append("=");
			 }
			 if(c == 2){
				 int32_t d1=data[data.length - 2] & 0xff;
				 int32_t d2=data[data.length - 1] & 0xff;
				 result.append(BASE64_ALPHABET[d1 >> 2]);
				 result.append(BASE64_ALPHABET[(d1 << 4 | d2 >> 4) & 0b111111]);
				 result.append(BASE64_ALPHABET[(d2 << 2) & 0b111111]);
				 result.append("=");
			 }
		 }
		 return result.toString();
	 }
	 String Util::readLine(InputStream & is){
		 int32_t read;
		 ByteArrayOutputStream baos;
		 while(true){
			 read=is.read();
			 if(read == '\n')break;
			 if(read < 0)break;
			 baos.write(read);
		 }
		 return baos.toString(UTF8).trim();
	 }
	 ByteArray Util::concat(const ByteArray & a, const ByteArray & b){
		 ByteArray result(a.length + b.length);
		 System::arraycopy(a, 0, result, 0, a.length);
		 System::arraycopy(b, 0, result, a.length, b.length);
		 return result;
	 }
}